Skip to content

feat: support multi-block conditions with declarative object syntax#3497

Draft
Copilot wants to merge 3 commits intomainfrom
copilot/support-multi-block-conditions
Draft

feat: support multi-block conditions with declarative object syntax#3497
Copilot wants to merge 3 commits intomainfrom
copilot/support-multi-block-conditions

Conversation

Copy link
Contributor

Copilot AI commented Mar 18, 2026

📝 Description

Adds a new object syntax for conditions that generates multiple independent CSS blocks from a single condition. Uses @slot markers to define where styles are inserted within each block.

⛳️ Current behavior (updates)

Conditions only support string or string[] formats, which nest into a single CSS block. No way to define a condition that produces two independent at-rule blocks — forcing users to either duplicate styles across two conditions or create per-property utilities with transform().

🚀 New behavior

New object syntax with @slot markers:

conditions: {
  extend: {
    hoverActive: {
      "@media (hover: hover)": { "&:is(:hover, [data-hover])": "@slot" },
      "@media (hover: none)": { "&:is(:active, [data-active])": "@slot" },
    },
  },
}
css({ _hoverActive: { bg: 'red' } })
@media (hover: hover) { .hoverActive\:bg_red:is(:hover, [data-hover]) { background: red; } }
@media (hover: none) { .hoverActive\:bg_red:is(:active, [data-active]) { background: red; } }

Single-block objects degrade to a MixedCondition, identical to existing array syntax.

Key changes:

  • packages/types/src/conditions.tsMultiBlockCondition type, ConditionObjectQuery type, ConditionQuery union updated
  • packages/core/src/parse-condition.tsparseObjectCondition() recursively collects paths to @slot markers, each becoming an independent block
  • packages/core/src/style-decoder.tsexpandMultiBlock() splits multi-block conditions into separate condition sets at decode time, reusing the same class selector across blocks
  • packages/core/src/sort-style-rules.tshasAtRule/flatten handle multi-block type
  • packages/config/src/validation/validate-condition.ts — recursive validation for object syntax
  • packages/generator/src/artifacts/js/conditions.ts — JSDoc generation handles object values

💣 Is this a breaking change (Yes/No):

No. Additive — existing string and string[] conditions are unaffected.

📝 Additional Information

Object nesting is recursive, so deeper structures work too. The parser collects every root-to-@slot path as an independent block. Same-media blocks are merged by PostCSS as expected.

Original prompt

This section details on the original issue you should resolve

<issue_title>Support multi-block conditions (OR-style conditions with different at-rules)</issue_title>
<issue_description>### Description

Allow a single condition to generate multiple independent CSS blocks, each wrapped in its own at-rule. This would enable defining hover-for-desktop + active-for-touch in one condition.

Problem Statement/Justification

When building responsive interactions, you often want hover feedback on devices with a pointer and active/press feedback on touch devices. This requires two separate @media blocks:

@media (hover: hover) { .el:hover { background: red; } }
@media (hover: none) { .el:active { background: red; } }

Currently, conditions only support [atRule, selector] pairs which nest into a single block. There's no way to define a condition that produces two independent blocks. We're forced to either:

  • Define two separate conditions (_hoverHover + _touchActive) and duplicate styles
  • Create per-property utilities with transform() that hardcode the media queries

Both workarounds scale poorly and lose the ergonomics of the condition system.

Proposed Solution or API

A new array-of-arrays syntax for conditions that generates multiple independent CSS blocks:

conditions: {
  extend: {
    hoverActive: [
      ['@media (hover: hover)', '&:is(:hover, [data-hover])'],
      ['@media (hover: none)', '&:is(:active, [data-active])'],
    ],
  },
}

Usage stays the same:

css({ _hoverActive: { bg: 'red' } })

Generated CSS:

@media (hover: hover) { .selector:is(:hover, [data-hover]) { background: red; } }
@media (hover: none) { .selector:is(:active, [data-active]) { background: red; } }

This is backward compatible — parseCondition already handles nested arrays as type: "mixed". The change would be to detect Array<[atRule, selector]> and emit separate rule blocks instead of nesting them.

Alternatives

  • Per-property custom utilities with transform() — works but requires a separate utility for each CSS property (bg, color, opacity, etc.)
  • Helper function that spreads styles into both conditions — works but TypeScript struggles with complex Panda style types
  • Using a single combined selector &:is(:hover, :active) — loses the media query isolation, causing ghost hover on touch devices

Additional Information

No response</issue_description>

<agent_instructions>I see the point, but I'm not a fan of this. The array-of-arrays pattern feels like an implementation detail leaking into the API surface.

I'd prefer we take this as an opportunity to redesign the conditions API to be more declarative. Something like this:

const config = {
  conditions: {
    extend: {
      anyHover: {
        "@media (hover: hover)": {
          "&:hover": "@slot",
        },
      },
      hoverActive: {
        "@media (hover: hover)": {
          "&:is(:hover, [data-hover])": "@slot",
        },
        "@media (hover: none)": {
          "&:is(:active, [data-active])": "@slot",
        },
      },
    },
  },
}
```</agent_instructions>

## Comments on the Issue (you are @copilot in this section)

<comments>
<comment_new><author>@anubra266</author><body>
I see the point, but I'm not a fan of this. The array-of-arrays pattern feels like an implementation detail leaking into the API surface.

I'd prefer we take this as an opportunity to redesign the conditions API to be more declarative. Something like this:

```tsx
const config = {
  conditions: {
    extend: {
      anyHover: {
        "@media (hover: hover)": {
          "&:hover": "@slot",
        },
      },
      hoverActive: {
        "@media (hover: hover)": {
          "&:is(:hover, [data-hover])": "@slot",
        },
        "@media (hover: none)": {
          "&:is(:active, [data-active])": "@slot",
        },
      },
    },
  },
}
```</body></comment_new>
</comments>

🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

@changeset-bot
Copy link

changeset-bot bot commented Mar 18, 2026

⚠️ No Changeset found

Latest commit: 0c2c0ac

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Mar 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
panda-docs Error Error Mar 18, 2026 7:28pm
panda-playground Error Error Mar 18, 2026 7:28pm
panda-studio Ready Ready Preview Mar 18, 2026 7:28pm

Request Review

…sing @slot markers

Co-authored-by: anubra266 <30869823+anubra266@users.noreply.github.com>
…, improve docs

Co-authored-by: anubra266 <30869823+anubra266@users.noreply.github.com>
Copilot AI changed the title [WIP] Add support for multi-block conditions with at-rules feat: support multi-block conditions with declarative object syntax Mar 18, 2026
Copilot AI requested a review from anubra266 March 18, 2026 19:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support multi-block conditions (OR-style conditions with different at-rules)

2 participants